package zzps.exeed.kernel.pandabuilder;

import zzps.exeed.kernel.annotation.Configuration;
import zzps.exeed.kernel.annotation.Import;
import zzps.exeed.kernel.annotation.Panda;
import zzps.exeed.kernel.core.ClassPathPandaEmbryoScanner;
import zzps.exeed.kernel.core.PandaContainer;
import zzps.exeed.kernel.core.PandaEmbryo;
import zzps.exeed.kernel.holder.*;
import zzps.exeed.kernel.pandainjector.PandaInjector;
import zzps.exeed.kernel.tool.StrTool;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;

public class ConfigurationPandaBuilder implements PandaBuilder<Configuration> {

    private PandaContainer pandaContainer;
    private Consumer<PandaHolder> pandaHolderConsumer;

    private ClassLoader classLoader;
    public ConfigurationPandaBuilder(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    @Override
    public void collect(Class<?> target, Configuration annotation, Consumer<PandaEmbryo> consumer) {
        PandaEmbryo<Configuration> pandaEmbryo = new PandaEmbryo(null,target,annotation,this);
        consumer.accept(pandaEmbryo);
        for (Annotation anno : target.getAnnotations()) {
            if (anno instanceof Import) {
                importFromImport((Import) anno);
            }
        }
        Method[] methods = target.getDeclaredMethods();
        for (Method method : methods) {
            Panda panda = method.getAnnotation(Panda.class);
            if (panda != null){
                MethodHolder methodHolder = new MethodHolder(null, method);//pandaHolder为空是因为下面构建的时候可以提供
                Map<MethodHolder, Panda> childs = pandaEmbryo.childs();
                if (childs == null){
                    childs = new HashMap<>(16);
                    pandaEmbryo.childs(childs);
                }
                childs.put(methodHolder,panda);
            }
        }
    }

    @Override
    public void build(PandaEmbryo<Configuration> pandaEmbryo, Consumer<PandaHolder> consumer, PandaContainer pandaContainer) {
        this.pandaContainer = pandaContainer;
        this.pandaHolderConsumer = consumer;
        PandaHolder pandaHolder = new PandaHolder(pandaEmbryo.clazz());
        consumer.accept(pandaHolder);
        Map<MethodHolder,Panda> childs = pandaEmbryo.childs();
        if (childs != null){
            for (Map.Entry<MethodHolder, Panda> entry : childs.entrySet()) {
                MethodHolder methodHolder = entry.getKey();
                Panda panda = entry.getValue();
                buildPanda(pandaHolder,methodHolder,panda);
            }
        }
    }
    private void importFromImport(Import anno) {
        for (Class<?> clazz : anno.value()) {
            pandaContainer.collectPanda(clazz);
        }
        for (String basePackage : anno.scanPackages()) {
            ClassPathPandaEmbryoScanner classPathPandaEmbryoScanner = new ClassPathPandaEmbryoScanner(classLoader);
            classPathPandaEmbryoScanner.setProcessor(pandaContainer::collectPanda);
            classPathPandaEmbryoScanner.scan(basePackage);
        }
        for (Class<?> src : anno.scanPackageClasses()) {
            String packageName = src.getPackageName();
            ClassPathPandaEmbryoScanner classPathPandaEmbryoScanner = new ClassPathPandaEmbryoScanner(classLoader);
            classPathPandaEmbryoScanner.setProcessor(pandaContainer::collectPanda);
            classPathPandaEmbryoScanner.scan(packageName);
        }
    }
    private void buildPanda(PandaHolder ph,MethodHolder methodHolder,Panda panda){
//        MethodHolder methodHolder = pandaBase.addMethodHolder(ph, method);
        ParameterHolder[] parameters = methodHolder.getParameters();
        if (parameters.length == 0){
            buildPanda(ph,methodHolder,new Object[]{},panda);
        }else {
            ValueGather valueGather = new ValueGather(parameters.length, objects -> buildPanda(ph,methodHolder,objects,panda));
            for (int i = 0; i < parameters.length; i++) {
                ParameterHolder parameterHolder = parameters[i];
                MethodArgumentValueInjector argumentValueInjector = new MethodArgumentValueInjector(parameterHolder);
                argumentValueInjector.setVarValueGather(valueGather);
                valueGather.getValueInjectors()[i] = argumentValueInjector;
                Annotation[] annotations = parameterHolder.getAnnotations();
                if (annotations.length == 0){
                    pandaContainer.injectPanda(argumentValueInjector,null);
                }else {
                    for (Annotation annotation : annotations) {
                        PandaInjector<Annotation> injector = (PandaInjector<Annotation>) pandaContainer.getPandaInjector(annotation.annotationType());
                        if (injector != null){
                            injector.inject(argumentValueInjector,annotation, pandaContainer::injectPanda);
                            return;
                        }
                    }
                    parameterHolder.setValue(null);
                }
            }
        }
    }
    private void buildPanda(PandaHolder ph,MethodHolder mh,Object[] args,Panda panda){
        Object o = mh.invoke(ph.raw(), args);
        Class<?> returnType = mh.getReturnType();
//        Type genericReturnType = mh.getGenericReturnType();
        PandaHolder pandaHolder = new PandaHolder(returnType, o, mh.get().getName());
        if (!StrTool.isEmpty(panda.name())){
            pandaHolder.setName(panda.name());
        }
        pandaHolderConsumer.accept(pandaHolder);
    }


}
